package emulator.assembler;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;

import emulator.hardware.HwWord;

public class SymbolTable {
	private HashMap<Integer, SymbolTableEntry> table = new HashMap<Integer, SymbolTableEntry>();
	private HashSet<Integer> jump_target_set = new HashSet<Integer>();
	private static int max_label_distance = 125;	// Branch distance (backwards)
	private static int max_zp_label_distance = 2; 	// Distance for self-modifying code
	
	public String getJumpLabel(int address) {
		String label = getSymbol(address);
		if (label == null && jump_target_set.contains(address))
			label = buildJumpLabelFromAddress(address);
		return label;
	}

	public String getLabelOrAddress(int address) {
		String label = getSymbol(address);
		return (label == null ? buildSymbolFromAddress(address) : label);
	}

	public String createJumpLabel(int address) {
		jump_target_set.add(address);
		return getJumpLabel(address);
	}

	public String getRemark(int address) {
		SymbolTableEntry entry = table.get(address);
		return (entry == null ? null : entry.getRemark());
	}

	private String getSymbol(int address) {
		SymbolTableEntry entry = table.get(address);
		return (entry == null ? null : entry.getLabel()); 
	}

	public void load(InputStream input_stream) {
		InputStreamReader reader = new InputStreamReader(input_stream);
	    BufferedReader in = new BufferedReader(reader);
	    String line = null;
	    try {
			while ((line = in.readLine()) != null)
			{
				SymbolTableEntry entry = SymbolTableEntry.createFromString(line);
				if (entry != null)
					table.put(entry.getAddress(), entry);
			}
		} catch (IOException e) {
		}
	}

	public void addSymbol(int address, String label, String remark, int type)
	{
		SymbolTableEntry entry = new SymbolTableEntry();
		entry.setAddress(address);
		entry.setLabel(label);
		entry.setRemark(remark);
		entry.setType(type);
		table.put(address, entry);
	}

	private String buildJumpLabelFromAddress(int address) {
		return "L_"+new HwWord(address);
	}

	private String buildSymbolFromAddress(int address) {
		SymbolTableEntry entry = table.get(address);
		int max_i = Math.min((address < 0x0100 ? max_zp_label_distance : max_label_distance),
							  address);
		int off = 0;
		for (int i = 1; entry == null && i <= max_i; i++)
		{
			entry = table.get(address - i);
			off = i;
		}
		if (entry != null)
		{
			if (entry.getLabel() != null)
				return (off == 0 ? entry.getLabel() : entry.getLabel() + "+" + Integer.toString(off)); 
		}
		return "$"+new HwWord(address);
	}

	public LinkedList<Integer> getCodeLabels() {
		LinkedList<Integer> code_labels = new LinkedList<Integer>();
		for (SymbolTableEntry entry : table.values())
		{
			if (entry.getType() == SymbolTableEntry.SYMBOL_TYPE_CODE)
				code_labels.push(entry.getAddress());
		}
		return code_labels;
	}

	public HashSet<Integer> getJumpTargets() {
		 return jump_target_set;		
	}

	public int findLabel(String search_text) 
	{
		int address = -1;
		for (SymbolTableEntry entry : table.values())
		{
			String label = entry.getLabel();
			if (label != null)
			{
				if (label.equalsIgnoreCase(search_text))
					return entry.getAddress();
			}
		}
		return address;
	}

}
